<!-- 动态标题组件 -->
<!-- 该组件可将字符串中的字符一个一个的显示（随机显示） -->
<!-- props     | type    | required | default | value      | 说明 -->
<!-- title     | String  | yes      | null    | -          | 用于显示的标题 -->
<!-- loop      | Boolean | no       | false   | true/false | 是否循环 -->
<!-- interval  | Number  | no       | 1000    | -          | 循环间隔 -->
<!-- shift     | String  | no       | null    | left/right | 是否漂移 -->
<!-- color     | String  | no       | #000000 | -          | 文字颜色 -->
<!-- font-size | String  | no       | 36px    | -          | 字体大小 -->
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'

interface Props {
  title: string
  loop?: boolean
  interval?: number
  shift?: string
  color?: string
  fontSize?: string
}

const props = withDefaults(defineProps<Props>(), {
  loop: false,
  interval: 1000,
  shift: void 0,
  color: '#000000',
  fontSize: '36px'
})

// 初始化组件自身数据
const cssColor = ref<string>(props.color)
const cssFontSize = ref<string>(props.fontSize)
const marginLeft = ref<string>(props.shift ? (props.shift === 'left' ? '20px' : '-20px') : '0')
const canShift = ref<boolean>(false)
if (props.shift) {
  setTimeout(() => {
    canShift.value = true
    marginLeft.value = '0'
  }, 0)
}

// 组件数据
const letterArr = ref<string[]>([])
const letterShowArr = ref<string[]>([])
const showIndex = ref<number[] | undefined>([])

// 组件动效控制器
const player = ref<any>(null)

const getShowIndex = () => {
  if (props.title) {
    const str = props.title
    const arr = []
    for (let i = 0; i < str.length; i++) {
      // eslint-disable-next-line no-constant-condition
      while (true) {
        const num = Math.floor(Math.random() * str.length)
        if (arr.indexOf(num) < 0) {
          arr.push(num)
          break
        }
      }
    }
    return arr
  }
  return void 0
}

const start = () => {
  if (!props.title) {
    return null
  }
  if (letterArr.value.length === 0) {
    letterArr.value = [...props.title]
  }
  if (showIndex.value?.length === 0) {
    showIndex.value = getShowIndex()
  }
  player.value = setInterval(() => {
    if (showIndex.value?.length && showIndex.value?.length > 0) {
      const index = showIndex.value?.pop()
      if (index !== void 0) {
        letterShowArr.value.push(letterArr.value[index])
      }
    } else {
      stop()
    }
  }, 100)
}

const stop = () => {
  if (player.value) {
    clearInterval(player.value)
    player.value = null
  }
}

watch(
  letterShowArr,
  (newVal) => {
    if (props.title && props.loop) {
      if (newVal.length === props.title.length) {
        setTimeout(() => {
          letterShowArr.value = []
          start()
        }, props.interval)
      }
    }
  },
  {
    deep: true
  }
)

onMounted(() => {
  start()
})

onUnmounted(() => {
  stop()
})
</script>

<template>
  <div class="dynamic-title-wrapper" :class="{ shift: canShift }">
    <template v-for="(letter, index) in letterArr" :key="index">
      <div class="letter-box">
        <span class="letter" :style="{ opacity: letterShowArr.indexOf(letter) > -1 ? 1 : 0 }">{{ letter }}</span>
      </div>
    </template>
  </div>
</template>

<style lang="scss" scoped>
.dynamic-title-wrapper {
  display: flex;
  color: v-bind(cssColor);
  font-size: v-bind(cssFontSize);
  margin-left: v-bind(marginLeft);
  .letter-box {
    width: calc(v-bind(cssFontSize) + 2px);
    .letter {
      transition: all 0.3s;
    }
  }
}
.dynamic-title-wrapper.shift {
  transition: margin-left 5s;
}
</style>
